﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Sio.Mdm.Server;
using Sio.Mdm.Client;
using System.Reflection;

namespace Sio.Mdm.Entities
{
    /// <summary>
    /// Attribute defining service and CRUD methods
    /// for entities retreival and updating
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
    public class EntityAttribute : Attribute
    {
        #region Fields
        private string _interfaceTypeName;
        internal bool CallByName = false;
        #endregion

        #region Construction
        /// <summary>
        /// Assign interface type implementing service buisness logic
        /// </summary>
        /// <param name="interfaceType">One of <see cref="IEntity"/> derived interface</param>
        public EntityAttribute(Type interfaceType)
        {
            _interfaceType = interfaceType;
        }
        /// <summary>
        /// Assign interface type by assembly qualified name as service
        /// implementing buisness logic for entity
        /// </summary>
        /// <param name="serviceTypeName">Assembly qualified name of interface type</param>
        public EntityAttribute(string serviceTypeName)
        {
            _interfaceTypeName = serviceTypeName;            
        }
        #endregion

        #region Properties

        #region InterfaceType
        private Type _interfaceType;
        /// <summary>
        /// Get name of service implementing functions
        /// for retreival and updating marked <see cref="EntityBase"/>
        /// </summary>
        public Type InterfaceType
        {
            get
            {                
                if (null == _interfaceType && !String.IsNullOrEmpty(_interfaceTypeName))
                {
                    _interfaceType = Type.GetType(_interfaceTypeName);
                }

                //if (null == _interfaceType)
                //{
                //    throw new ArgumentException(
                //        String.Format("Unable to load {0} for service type", _interfaceTypeName)
                //    );
                //}

                return _interfaceType;
            }
            internal set
            {
                _interfaceType = value;
            }
        }
        #endregion

        #region InterfaceTypeName
        /// <summary>
        /// Get either type of interface for service or service name for manipulating entity
        /// </summary>
        public string InterfaceTypeName
        {
            get
            {
                return _interfaceTypeName;
            }
        } 
        #endregion

        #region CreateMethod
        private string _createMethod = "Create";
        public string CreateMethod
        {
            get
            {
                return _createMethod;
            }
            set
            {
                _createMethod = value;
            }
        }
        #endregion

        #region DeleteMethod
        private string _deleteMethod = "Delete";
        public string DeleteMethod
        {
            get
            {
                return _deleteMethod;
            }
            set
            {
                _deleteMethod = value;
            }
        }
        #endregion

        #region UpdateMethod
        private string _updateMethod = "Update";
        public string UpdateMethod
        {
            get
            {
                return _updateMethod;
            }
            set
            {
                _updateMethod = value;
            }
        }
        #endregion

        #region SelectMethod
        private string _selectMethod = "Select";
        public string SelectMethod
        {
            get
            {
                return _selectMethod;
            }
            set
            {
                _selectMethod = value;
            }
        }
        #endregion

        #region TableName
        private string _tableName;
        /// <summary>
        /// Get or set table name in storage to use for selecting entity
        /// </summary>
        public string TableName
        {
            get
            {
                return _tableName;
            }
            set
            {
                _tableName = value;
            }
        }
        #endregion

        #region DMLTableName
        private string _DmlTableName;
        /// <summary>
        /// Get or set table data manipulaction table name in storage to use for delete, update, insert operations
        /// </summary>
        public string DmlTableName
        {
            get
            {
                return _DmlTableName??_tableName;
            }
            set
            {
                _DmlTableName = value;
            }
        }
        #endregion

        #region Lazy
        private bool _lazy = true;
        /// <summary>
        /// Get or set wheater entity should use lazy updating and loading.
        /// That is any changes to the entity will not be reflected to datastore
        /// until save or update method is invoked.
        /// </summary>
        public bool Lazy
        {
            get
            {
                return _lazy;
            }
            set
            {
                _lazy = value;
            }
        } 
        #endregion

        #region GeneratorName
        public string GeneratorName
        {
            get;
            set;
        } 
        #endregion

        #endregion
    }

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
    public class StoredProcedureEntityAttribute : EntityAttribute
    {
        #region Construction
        public StoredProcedureEntityAttribute(Type interfaceType): base(interfaceType)
        { }
        public StoredProcedureEntityAttribute(string serviceTypeName): base (serviceTypeName)
        { }
        #endregion

        #region StoredProcedureName
        private string _storedProcedureName;
        /// <summary>
        /// Get or set table name in storage to use for selecting entity
        /// </summary>
        public string StoredProcedureName
        {
            get
            {
                return _storedProcedureName;
            }
            set
            {
                _storedProcedureName = value;
            }
        }
        #endregion
    }

    [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
    public class EntitySettingsAttribute : EntityAttribute
    {
        public EntitySettingsAttribute()
            : base("SettingsService")
        {
        }
    }
        
    /// <summary>
    /// Attribute defining <see cref="IEntity"/> field
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class FieldAttribute : Attribute
    {
        #region Properties

        #region Name
        private string _name;
        /// <summary>
        /// Get or set name of field in database. This is only neccessary
        /// when property name is not same as name of field in database.
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }
        #endregion

        #region Type
        private DbType _type;
        /// <summary>
        /// Get or set database type of field. Tis only necessary
        /// for custom data types. Basic value types are automaticly converted
        /// by framework.
        /// </summary>
        public DbType FieldDbType
        {
            get
            {
                return _type;
            }
            set
            {
                _type = value;
            }
        } 
        #endregion

        #region DeclaringTable
        private string _declaringTable;
        /// <summary>
        /// Get or set table name, where field is declared. This is usefull
        /// for "overloaded" primary keys
        /// </summary>
        public string DeclaringTable
        {
            get
            {
                return _declaringTable;
            }
            set
            {
                _declaringTable = value;
            }
        }
        #endregion


        #region FieldType
        /// <summary>
        /// Get or set field type
        /// </summary>
        public Type FieldType { get; set; } 
        #endregion

        #region LoadOnDemand
        private bool _loadOnDemand = true;
        /// <summary>
        /// If true any nested entities are loaded on first retreival of property.
        /// This will of course only work for entities that are loaded.
        /// By default this value is set to true.
        /// </summary>
        /// <seealso cref="EntityState"/>
        public bool LoadOnDemand
        {
            get
            {
                return _loadOnDemand;
            }
            set
            {
                _loadOnDemand = value;
            }
        }
        #endregion 

        #region AllowNull
        private bool _allowNull = true;
        /// <summary>
        /// Get or set wheater null value is allowed for field. By default this is true.
        /// </summary>
        public bool AllowNull
        {
            get
            {
                return _allowNull;
            }
            set
            {
                _allowNull = value;
            }
        } 
        #endregion

        #region DefaultValue
        private object _default;
        public object DefaultValue
        {
            get
            {   
                return _default;
            }
            set
            {
                _default = value;
            }
        }
        #endregion

        #region CreateMethod
        private string _createMethod = "Create";
        public string CreateMethod
        {
            get
            {
                return _createMethod;
            }
            set
            {
                _createMethod = value;
            }
        }
        #endregion

        #region DeleteMethod
        private string _deleteMethod = "Delete";
        public string DeleteMethod
        {
            get
            {
                return _deleteMethod;
            }
            set
            {
                _deleteMethod = value;
            }
        }
        #endregion

        #region UpdateMethod
        private string _updateMethod = "Update";
        public string UpdateMethod
        {
            get
            {
                return _updateMethod;
            }
            set
            {
                _updateMethod = value;
            }
        }
        #endregion

        #region SelectMethod
        private string _selectMethod = "Select";
        public string SelectMethod
        {
            get
            {
                return _selectMethod;
            }
            set
            {
                _selectMethod = value;
            }
        }
        #endregion

        #region UpadateValue
        private object _updateValue = null;
        public object UpdateValue
        {
            get
            {
                return _updateValue;
            }
            set
            {
                _updateValue = value;
            }
        }
        #endregion

        #region ForeignKey
        public bool ForeignKey
        {
            get;
            set;
        } 
        #endregion

        #endregion
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class PrimaryKeyAttribute : FieldAttribute
    {
        public PrimaryKeyAttribute()
        {
            AllowNull = false;
        }

        #region Properties
        /// <summary>
        /// Generator name in database to gen next value
        /// </summary>
        private string _generatorName;

        public string GeneratorName
        {
            get { return _generatorName; }
            set { _generatorName = value; }
        }

        #region IsIdentity
        private bool _isIdentity = false;
        /// <summary>
        /// Signals wheater field is identity. Usefull when
        /// doing insert. Entity will get reloaded will newly created ID.
        /// </summary>
        public bool IsIdentity
        {
            get
            {
                return _isIdentity;
            }
            set
            {
                _isIdentity = value;
            }
        }  
        #endregion

        #region UpdateWhenEmpty
        private bool _updateWhenEmtpy = false;
        public bool UpdateWhenEmpty
        {
            get
            {
                return _updateWhenEmtpy;
            }
            set
            {
                _updateWhenEmtpy = value;
            }
        }
        #endregion

        #endregion
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class DeletedStatusAttribute : FieldAttribute
    {
        public DeletedStatusAttribute()
        {
        }

        #region DeclaringTableName
        private string _declaringTableName;
        /// <summary>
        /// Get or set raw table name in storage to use for selecting entity. It is raw tablename where field is declared.
        /// </summary>
        public string DeclaringTableName
        {
            get
            {
                return _declaringTableName;
            }
            set
            {
                _declaringTableName = value;
            }
        }
        #endregion
    }

    /// <summary>
    /// Attribute to mark events on entites that should fire when
    /// either single field is changed or any field is changed
    /// </summary>
    [AttributeUsage(AttributeTargets.Event)]
    public class NotifyChangeAttribute : Attribute
    {
        #region Construction
        public NotifyChangeAttribute()
            : this(String.Empty)
        {
        }
        public NotifyChangeAttribute(string fieldName)
        {
            _fieldName = fieldName;
        } 
        #endregion

        #region Properties

        #region FieldName
        private string _fieldName;
        /// <summary>
        /// Name of field to monitor for changes. Leaving this empty will monitor all 
        /// field changes on <see cref="IEntity"/>
        /// </summary>
        public string FieldName
        {
            get
            {
                return _fieldName;
            }
        } 
        #endregion

        #region EventName
        private string _eventName;
        /// <summary>
        /// Name of event to fire. This is for internal framework use only 
        /// and any value set to this attribute will have no effect
        /// </summary>
        public string EventName
        {
            get
            {
                return _eventName;
            }
            set
            {
                _eventName = value;
            }
        }  
        #endregion

        #endregion
    }
    
    [AttributeUsage(AttributeTargets.Property)]
    public class SelfReferenceFieldAttribute : FieldAttribute
    {
        public SelfReferenceFieldAttribute()
        {
        }
        public SelfReferenceFieldAttribute(string columnName)
        {
            Name = columnName;
        }

        private string _fieldName;
        public string ReferenceField
        {
            get
            {
                return base.Name;
            }
            set
            {
                base.Name = value;
            }
        }
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class RelationAttribute : Attribute
    {
        public string SourceField { get; set; }
        public string SourceTable { get; set; }
        public string DestinationField { get; set; }
        public string DestinationTable { get; set; }
    }

    [AttributeUsage(AttributeTargets.Method)]
    public class MethodAttribute : Attribute
    {
        #region Construction
        public MethodAttribute()
        {
        }
        public MethodAttribute(string methodName)
        {
            if (String.IsNullOrEmpty(methodName))
            {
                throw new ArgumentNullException("methodName");
            }

            MethodName = methodName;
        }
        public MethodAttribute(string methodName, string serviceName)
            : this(methodName)
        {
            ServiceName = serviceName;
        }
        public MethodAttribute(string methodName, Type serviceType)
            : this(methodName)
        {
            ServiceType = serviceType;
        } 
        #endregion

        #region Properties
        
        #region Public
        public string MethodName { get; set; }
        public string ServiceName { get; set; }        
        #endregion

        #region Internal
        internal Type ServiceType { get; set; }
        internal Type EntityType { get; set; }
        #endregion

        #endregion

        #region Methods
        #region Internal
        internal object InvokeMethod(IEntity entity, params object[] args)
        {
            int count = args.Length + 2;
            object[] arguments = new object[count];

            for (int i = 0; i < args.Length; i++)
            {
                arguments[i] = args[i];
            }
            
            arguments[args.Length] = entity;
            arguments[args.Length + 1] = Sio.Mdm.Types.Session.Current;

            IService service = ServiceFactory.GetService(ServiceType);
            object result = null;

            try
            {
                result = ServiceType.InvokeMember(
                    MethodName,
                    BindingFlags.InvokeMethod,
                    null,
                    service,
                    arguments
                );
            }
            catch (Exception ex)
            {
                ex.ToString();
            }

            return result;
        }
        #endregion
        #endregion
    }

    /// <summary>
    /// Definition of event for notifiying user about changes of <see cref="IEntity"/> fields
    /// </summary>
    /// <param name="entity">Entity instance that was changed</param>
    /// <param name="fieldName">Name of field that was changed</param>
    /// <seealso cref="NotifyChangeAttribute"/>
    /// <seealso cref="IEnityt"/>
    public delegate void FieldChangedEvent(IEntity entity, string fieldName);
}
